﻿#include <string>
#include <cassert>
#include <QDebug>
#include "DataMatrixDecoder.h"

static DmtxImage* createDmtxImageFormQImage(QImage &image)
{
    DmtxImage* img = nullptr;
    switch (image.format())
    {
    case QImage::Format_RGB32:
    case QImage::Format_ARGB32:
    case QImage::Format_ARGB32_Premultiplied:
        img = dmtxImageCreate(image.bits(), image.width(), image.height(), DmtxPack32bppXRGB);
        img->rowSizeBytes = image.bytesPerLine();
        break;
    case QImage::Format_RGB888:
        img = dmtxImageCreate(image.bits(), image.width(), image.height(), DmtxPack24bppRGB);
        img->rowSizeBytes = image.bytesPerLine();
        break;
    case QImage::Format_Grayscale8:
        img = dmtxImageCreate(image.bits(), image.width(), image.height(), DmtxPack8bppK);
        img->rowSizeBytes = image.bytesPerLine();
        break;
    default:
        break;
    }
    return img;
}

QString DataMatrixDecoder::decode(QImage &image)
{
    QString str;
    DmtxTime timeout;

    /* Reset timeout for each new page */
    if(m_opt.timeoutMS != DmtxUndefined)
    {
        timeout = dmtxTimeAdd(dmtxTimeNow(), m_opt.timeoutMS);
    }
    DmtxImage* img = createDmtxImageFormQImage(image);
    if(img == nullptr)
    {
        qFatal("dmtxImageCreate() error");
    }

    dmtxImageSetProp(img, DmtxPropImageFlip, DmtxFlipNone);

    DmtxDecode* dec = dmtxDecodeCreate(img, m_opt.shrinkMin);
    DmtxPassFail err = setDecodeOptions(dec, m_opt);
    if(err != DmtxPass)
    {
        qFatal("decode option error");
    }
    DmtxRegion* reg = nullptr;
    if(m_opt.timeoutMS == DmtxUndefined)
       reg = dmtxRegionFindNext(dec, nullptr);
    else
       reg = dmtxRegionFindNext(dec, &timeout);
    if (reg != NULL)
    {
        DmtxMessage* msg = dmtxDecodeMatrixRegion(dec, reg, m_opt.correctionsMax);
        if (msg != NULL)
        {
            str = QString::fromLatin1((const char *)msg->output);
            dmtxMessageDestroy(&msg);
        }
        else
            qDebug() << "Not valid data type.";
        dmtxRegionDestroy(&reg);
    }
    return str;
}

QStringList DataMatrixDecoder::decodeMulti(QImage &image)
{
    QStringList list;
    DmtxTime timeout;

    /* Reset timeout for each new page */
    if(m_opt.timeoutMS != DmtxUndefined)
    {
        timeout = dmtxTimeAdd(dmtxTimeNow(), m_opt.timeoutMS);
    }
    /* Initialize libdmtx image */
    DmtxImage* img = createDmtxImageFormQImage(image);
    if(img == nullptr)
    {
        qFatal("dmtxImageCreate() error");
    }

    dmtxImageSetProp(img, DmtxPropImageFlip, DmtxFlipNone);

    /* Initialize scan */
    DmtxDecode* dec = dmtxDecodeCreate(img, m_opt.shrinkMin);
    if(dec == nullptr)
    {
        qFatal("dmtxDecodeCreate() error");
    }
    DmtxPassFail err = setDecodeOptions(dec, m_opt);
    if(err != DmtxPass)
    {
        qFatal("decode option error");
    }
    int imgScanCount = 0;
    for(;;)
    {
        DmtxRegion* reg = nullptr;
        /* Find next barcode region within image, but do not decode yet */
        if(m_opt.timeoutMS == DmtxUndefined)
           reg = dmtxRegionFindNext(dec, nullptr);
        else
           reg = dmtxRegionFindNext(dec, &timeout);

        /* Finished file or ran out of time before finding another region */
        if(reg == nullptr)
           break;

        /* Decode region based on requested barcode mode */
        DmtxMessage* msg = nullptr;
        if(m_opt.mosaic == DmtxTrue)
        {
           msg = dmtxDecodeMosaicRegion(dec, reg, m_opt.correctionsMax);
        }
        else
        {
           msg = dmtxDecodeMatrixRegion(dec, reg, m_opt.correctionsMax);
        }

        if(msg != nullptr)
        {
            list << QString::fromLatin1((const char *)msg->output);
            imgScanCount++;
            dmtxMessageDestroy(&msg);
        }
        dmtxRegionDestroy(&reg);
        if(m_opt.stopAfter != DmtxUndefined && imgScanCount >= m_opt.stopAfter)
        {
            break;
        }
    }
    dmtxDecodeDestroy(&dec);
    dmtxImageDestroy(&img);
    return list;
}

void DataMatrixDecoder::setOptions(UserOptions &opt)
{
    m_opt = opt;
}

DataMatrixDecoder::UserOptions DataMatrixDecoder::options() const
{
    return m_opt;
}

DmtxPassFail DataMatrixDecoder::setDecodeOptions(DmtxDecode *dec, UserOptions &opt)
{
    int err;

 #define RETURN_IF_FAILED(e) if(e != DmtxPass) { return DmtxFail; }

    err = dmtxDecodeSetProp(dec, DmtxPropScanGap, opt.scanGap);
    RETURN_IF_FAILED(err)

    if(opt.gs1 != DmtxUndefined) {
       err = dmtxDecodeSetProp(dec, DmtxPropFnc1, opt.gs1);
       RETURN_IF_FAILED(err)
    }

    if(opt.edgeMin != DmtxUndefined) {
       err = dmtxDecodeSetProp(dec, DmtxPropEdgeMin, opt.edgeMin);
       RETURN_IF_FAILED(err)
    }

    if(opt.edgeMax != DmtxUndefined) {
       err = dmtxDecodeSetProp(dec, DmtxPropEdgeMax, opt.edgeMax);
       RETURN_IF_FAILED(err)
    }

    if(opt.squareDevn != DmtxUndefined) {
       err = dmtxDecodeSetProp(dec, DmtxPropSquareDevn, opt.squareDevn);
       RETURN_IF_FAILED(err)
    }

    err = dmtxDecodeSetProp(dec, DmtxPropSymbolSize, opt.sizeIdxExpected);
    RETURN_IF_FAILED(err)

    err = dmtxDecodeSetProp(dec, DmtxPropEdgeThresh, opt.edgeThresh);
    RETURN_IF_FAILED(err)

    if(opt.xMin) {
       err = dmtxDecodeSetProp(dec, DmtxPropXmin, opt.xMin);
       RETURN_IF_FAILED(err)
    }

    if(opt.xMax) {
       err = dmtxDecodeSetProp(dec, DmtxPropXmax, opt.xMax);
       RETURN_IF_FAILED(err)
    }

    if(opt.yMin) {
       err = dmtxDecodeSetProp(dec, DmtxPropYmin, opt.yMin);
       RETURN_IF_FAILED(err)
    }

    if(opt.yMax) {
       err = dmtxDecodeSetProp(dec, DmtxPropYmax, opt.yMax);
       RETURN_IF_FAILED(err)
    }

 #undef RETURN_IF_FAILED

    return DmtxPass;

}

void DataMatrixDecoder::restoreDefaultOption(UserOptions &opt)
{
    memset(&opt, 0x00, sizeof(UserOptions));

    /* Default options */
    opt.edgeMin = DmtxUndefined;
    opt.edgeMax = DmtxUndefined;
    opt.scanGap = 2;
    opt.timeoutMS = DmtxUndefined;
    opt.squareDevn = DmtxUndefined;
    opt.sizeIdxExpected = DmtxSymbolShapeAuto;
    opt.edgeThresh = 5;
    opt.xMin = 0;
    opt.xMax = 0;
    opt.yMin = 0;
    opt.yMax = 0;
    opt.correctionsMax = DmtxUndefined;
    opt.mosaic = DmtxFalse;
    opt.stopAfter = DmtxUndefined;
    opt.shrinkMin = 1;
    opt.gs1 = DmtxUndefined;
}

void DataMatrixDecoder::setTimeout(int ms)
{
    if(ms > 0)
    {
        m_opt.timeoutMS = ms;
    }
    else
    {
        m_opt.timeoutMS = DmtxUndefined;
    }
}

DataMatrixDecoder::DataMatrixDecoder()
{
    restoreDefaultOption(m_opt);
    //m_opt.timeoutMS = 500;
}
